package cn.chinaunicom.mini.program.dingtalk.events.publisher;

import cn.chinaunicom.mini.program.dingtalk.annotation.DTEvent;
import cn.chinaunicom.mini.program.dingtalk.events.event.DingTalkEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;

/**
 * @ProjectName: dingtalk-adapter
 * @Package: cn.chinaunicom.mini.program.dingtalk.events.publisher
 * @ClassName: ComponentScanner
 * @Description: 事件类型扫描器
 * @Author: wangjc
 * @CreateDate: 2024/7/4
 * @UpdateUser: wangjc
 * @UpdateDate: 2024/7/4
 * @UpdateRemark: 更新内容
 * @Version: 1.0
 */

@Slf4j
@Component
public class EventTypeScanner {

    /**
     * 扫描指定包中注解了特定注解的类，并将其映射到事件类型上。
     *
     * 此方法用于自动发现并注册标记了特定注解（如{@link DTEvent}）的钉钉事件处理类。
     * 它通过扫描指定的基本包，找出所有包含指定注解的类，并将这些类与它们在注解中声明的事件类型关联起来。
     * 这样做的目的是为了在运行时能够根据接收到的钉钉事件类型，快速找到对应的事件处理类。
     *
     * @param basePackage 扫描的包的基路径。所有在这个包及其子包下的类都会被扫描。
     * @param annotationClass 指定的注解类。只有被这个注解标记的类才会被收集。
     * @return 返回一个映射表，键是事件类型字符串，值是对应事件处理类。这个映射表用于后续的事件路由。
     */
    public Map<String, Class<? extends DingTalkEvent>> scanEventTypeClasses(String basePackage, Class<? extends Annotation> annotationClass) {
        // 初始化一个映射表，用于存储事件类型到事件处理类的映射。
        Map<String, Class<? extends DingTalkEvent>> eventTypes = new HashMap<>();

        // 创建一个类路径扫描器，用于查找标记了指定注解的类。
        ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
        // 添加一个过滤器，只包含标记了指定注解的类。
        provider.addIncludeFilter(new AnnotationTypeFilter(annotationClass));

        // 在指定的包下查找所有候选组件（即类）。
        Set<BeanDefinition> beanDefs = provider.findCandidateComponents(basePackage);
        for (BeanDefinition beanDef : beanDefs) {
            invoke(annotationClass, beanDef, eventTypes);
        }
        log.info("初始化钉钉事件订阅完成，事件类型加载总数为：{}", eventTypes.size());
        // 返回构建的映射表。
        return eventTypes;
    }

    /**
     * 根据注解类别查找并处理特定事件类型的类。
     * 此方法的目的是通过反射机制加载指定Bean定义的类，并检查该类是否注解了特定的DingTalk事件注解。
     * 如果类被注解，并且注解的事件类型在事件映射中不存在，则将该类与事件类型映射起来。
     *
     * @param annotationClass 需要检查的注解类，用于筛选类。
     * @param beanDef Bean的定义，包含待检查类的信息。
     * @param eventTypes 事件类型与对应处理类的映射表，用于存储找到的事件处理类。
     */
    private void invoke(Class<? extends Annotation> annotationClass, BeanDefinition beanDef, Map<String, Class<? extends DingTalkEvent>> eventTypes) {
        try {
            log.debug("当前扫描到的类名称：{}", beanDef.getBeanClassName());
            // 加载类，并检查它是否实现了DingTalkEvent接口。
            Class<? extends DingTalkEvent> clazz = (Class<? extends DingTalkEvent>) Class.forName(beanDef.getBeanClassName());
            // 获取类上标记的DTEvent注解。
            DTEvent dt = clazz.getAnnotation(DTEvent.class);
            // 如果类没有标记DTEvent注解，则跳过。
            if (Objects.nonNull(dt)) {
                // 将事件类型与处理类添加到映射表中。
                // 事件类型多个时，循环注册事件
                Stream.of(dt.value()).forEach(type -> eventTypes.put(type.getType(), clazz));
            }
        } catch (ClassNotFoundException e) {
            // 记录类找不到的错误。
            log.error("Error scanning classes with annotation: {}", annotationClass.getName(), e);
        }
    }
}
